/*
 * Copyright (C) 2019-2020 Alibaba Group Holding Limited
 */

#include <mediaplayer.h>
#include <avutil/misc.h>
#include <csi_core.h>

#define TAG "mediaplayer"

typedef int8_t state_func_t();
static int8_t  do_state_play();
static int8_t  do_state_pause();
static int8_t  do_state_zero_volume();
static int8_t  do_state_mute();

static state_func_t *const func_table[NUM_STATES] = {do_state_play, do_state_pause,
                                                     do_state_zero_volume, do_state_mute};

typedef struct mplayer_type {
    utask_t *       task_media;
    mplayer_state_t cur_state;
    mplayer_state_t last_state;
    uint8_t         min_vol;
    uint8_t         lpm_flag; // 0: not in lpm state, 1: in lpm state
    media_evt_t     local_cb;
    aos_event_t     event_tts_state;
    uint8_t         tts_resume;
} mplayer_type_t;

static mplayer_type_t mplayer_ctx;
#define EVENT_TTS_PLAYER_START (1 << 0)
#define EVENT_TTS_PLAYER_FINISHED (1 << 1)

#define LPM_RETURN()                                                                               \
    if (mplayer_ctx.lpm_flag) {                                                                    \
        return;                                                                                    \
    }
#define LPM_RETURN_RET(ret)                                                                        \
    if (mplayer_ctx.lpm_flag) {                                                                    \
        return ret;                                                                                \
    }

/***** internal APIs *****/
static void _aui_evt_cb_(int type, aui_player_evtid_t evt_id)
{
    switch (evt_id) {
        case AUI_PLAYER_EVENT_START:
            if (MEDIA_SYSTEM == type) {
                aos_event_set(&mplayer_ctx.event_tts_state, EVENT_TTS_PLAYER_START, AOS_EVENT_OR);
            }
            break;
        case AUI_PLAYER_EVENT_ERROR:
        case AUI_PLAYER_EVENT_FINISH:
            if (MEDIA_SYSTEM == type) {
                aos_event_set(&mplayer_ctx.event_tts_state, EVENT_TTS_PLAYER_FINISHED,
                              AOS_EVENT_OR);
                event_publish(EVENT_TTS_PLAYER_FINISHED, NULL);
            }
            break;
        default:
            break;
    }

    if (NULL != mplayer_ctx.local_cb) {
        mplayer_ctx.local_cb(type, evt_id);
    }
}

static void _mplayer_evt_cb(uint32_t event_id, const void *param, void *context)
{
    switch (event_id) {
        case EVENT_TTS_PLAYER_FINISHED:
            if ((AUI_PLAYER_PAUSED == aui_player_get_state(MEDIA_MUSIC)) &&
                mplayer_ctx.tts_resume && (MPLAYER_STATE_PLAYING == mplayer_ctx.cur_state)) {
                /* 确认当前状态是被打断且可恢复状态 */
                aui_player_resume(MEDIA_MUSIC);
            }
            break;
        default:;
    }
}

static int ai_kv_get_vol(int type)
{
    char key[8];
    snprintf(key, sizeof(key), "vol%d", type);

    int value;
    int ret = aos_kv_getint(key, &value);
    return ret == 0 ? value : 30;
}

static int ai_kv_set_vol(int type, int vol)
{
    char key[8];
    snprintf(key, sizeof(key), "vol%d", type);
    return aos_kv_setint(key, vol);
}

static int media_dec_key_cb(const void *in, size_t ilen, void *out, size_t *olen)
{
    int rc;

    CHECK_PARAM(in && ilen >= 3 && out && olen && *olen >= 16, -1);

    LOGD(TAG, "mp4 major brand is %s", in);
    char cenc_decrypt_key[33] = {0}; // "6ABED02448B8FFD2224FF54619935526"

    if (aos_kv_getstring("mp4_key", cenc_decrypt_key, 33) > 0) {
        rc = bytes_from_hex(cenc_decrypt_key, out, *olen);
        CHECK_RET_TAG_WITH_RET(rc == 0, -1);
        *olen = 16;
        return 0;
    }

    LOGW(TAG, "major brand %s get key failed", in);
    return -1;
}

static int media_init(media_evt_t media_evt_cb)
{
    int ret = -1;

    ret                  = aui_player_init(mplayer_ctx.task_media, _aui_evt_cb_);
    mplayer_ctx.local_cb = media_evt_cb;

    if (ret == 0) {
        aui_player_config_t config = {0};

        config.resample_rate = 48000;
        //config.web_cache_size = 160 * 1024;  /* 修改网络传输的缓冲默认80K */
        aui_player_config(&config);
    }

    event_subscribe(EVENT_TTS_PLAYER_FINISHED, _mplayer_evt_cb, NULL);

    return ret;
}

/**
 * 音量变化时调用该函数进行保存
*/
static int auikv_player_vol_save(void)
{
    int vol_music = 0, vol_sys = 0;

    vol_music = aui_player_vol_get(MEDIA_MUSIC);
    vol_sys   = aui_player_vol_get(MEDIA_SYSTEM);
    if (vol_music != vol_sys) {
        /* music and system should have same volumn */
        LOGW(TAG, "music volume(%d) is different with system volume(%d)", vol_music, vol_sys);
    }
    ai_kv_set_vol(MEDIA_ALL, vol_sys);
    return vol_music;
}

static int volume_set(int vol)
{
    aui_player_vol_set(MEDIA_ALL, vol);
    return auikv_player_vol_save();
}

static void switch_state(mplayer_state_t old_state, mplayer_state_t new_state)
{
    if (old_state == new_state) {
        /* don't need to change state */
        return;
    }

    LOGD(TAG, "old state[%d] --> new state[%d]", old_state, new_state);

    if (MPLAYER_STATE_MUTE == old_state) {
        mplayer_ctx.last_state = MPLAYER_STATE_PLAYING;
    } else {
        mplayer_ctx.last_state = old_state;
    }
    mplayer_ctx.cur_state = new_state;
    func_table[mplayer_ctx.cur_state]();
}

static int8_t stop_play(int type)
{
    if ((MEDIA_MUSIC == type) || (MEDIA_ALL == type)) {
        aui_player_stop(MEDIA_MUSIC);
        switch_state(mplayer_ctx.cur_state, MPLAYER_STATE_PLAYING);
    }

    if ((MEDIA_SYSTEM == type) || (MEDIA_ALL == type)) {
        aui_player_stop(MEDIA_SYSTEM);
    }

    return 0;
}

static int8_t do_state_play()
{
    if (AUI_PLAYER_PLAYING != aui_player_get_state(MEDIA_SYSTEM)) {
        /* 如果系统声音正在播放，系统声音播放结束后会恢复音乐 */
        aui_player_resume(MEDIA_MUSIC);
    }
    return 0;
}

static int8_t do_state_pause()
{
    aui_player_state_t play_state;
    play_state = aui_player_get_state(MEDIA_MUSIC);
    if (AUI_PLAYER_PLAYING == play_state) {
        aui_player_pause(MEDIA_MUSIC);
    }

    return 0;
}

static int8_t do_state_zero_volume()
{
    /* check current volume, which should be zero */
    int vol = aui_player_vol_get(MEDIA_MUSIC);
    if (vol != 0) {
        LOGE(TAG, "current volume[%d] is not zero!", vol);
        return -1;
    }
    aui_player_state_t play_state;
    play_state = aui_player_get_state(MEDIA_MUSIC);
    if (AUI_PLAYER_PLAYING == play_state) {
        aui_player_pause(MEDIA_MUSIC);
    }

    return 0;
}

static int8_t do_state_mute()
{
    int ret = 0;
    ret     = aui_player_pause(MEDIA_MUSIC);
    ret     = stop_play(MEDIA_SYSTEM);

    return ret;
}

/****************************************/
/*          external APIs               */
/****************************************/
/**
 * 从kv中读取初始化播放器音量
*/
int8_t mplayer_init(size_t stack_size, media_evt_t media_evt_cb, uint8_t min_vol)
{
    LOGD(TAG, "Enter %s", __FUNCTION__);
    int ret = 0;
    int vol;

    if (NULL != mplayer_ctx.task_media) {
        LOGW(TAG, "media task is already inited!");
        return 0;
    }

    memset(&mplayer_ctx, 0, sizeof(mplayer_type_t));
    mplayer_ctx.task_media =
        utask_new("task_media", stack_size, QUEUE_MSG_COUNT, AOS_DEFAULT_APP_PRI);
    if (NULL == mplayer_ctx.task_media) {
        LOGE(TAG, "media task created failed!");
        return -EINVAL;
    }
    media_init(media_evt_cb);
    aos_event_new(&mplayer_ctx.event_tts_state, 0);

    aui_player_key_config(media_dec_key_cb);

    vol = ai_kv_get_vol(MEDIA_ALL);
    aui_player_vol_set(MEDIA_ALL, vol);

    mplayer_ctx.min_vol = (min_vol <= 0) ? 20 : min_vol;
    aui_player_set_minvol(MEDIA_ALL, 0);

    mplayer_ctx.cur_state = mplayer_ctx.last_state = MPLAYER_STATE_PLAYING;
    if (0 == vol) {
        mplayer_ctx.cur_state = MPLAYER_STATE_ZERO_VOLUME;
    }

    return ret;
}

int8_t mplayer_vol_up(mplayer_source_t source, int16_t vol)
{
    int cur_vol = 0;
    int ret     = 0;

    cur_vol = aui_player_vol_get(MEDIA_MUSIC);
    ret     = mplayer_vol_set(source, cur_vol + vol);
    return ret;
}

int8_t mplayer_vol_down(mplayer_source_t source, int16_t vol)
{
    int cur_vol = 0;
    int ret     = 0;

    cur_vol = aui_player_vol_get(MEDIA_MUSIC);
    ret     = mplayer_vol_set(source, cur_vol - vol);
    return ret;
}

int8_t mplayer_vol_set(mplayer_source_t source, int16_t set_vol)
{
    LPM_RETURN_RET(-1);
    LOGD(TAG, "Enter %s: current state [%d]", __FUNCTION__, mplayer_ctx.cur_state);
    int ret = 0;
    int vol;
    switch (mplayer_ctx.cur_state) {
        case MPLAYER_STATE_PLAYING:
        case MPLAYER_STATE_PAUSE:
            vol = volume_set(set_vol);
            if (0 == vol) {
                switch_state(mplayer_ctx.cur_state, MPLAYER_STATE_ZERO_VOLUME);
            } else if (vol > 0) {
                mplayer_ctx.last_state = MPLAYER_STATE_PLAYING;
            }
            break;
        case MPLAYER_STATE_ZERO_VOLUME:
            vol = volume_set(set_vol);
            if (vol > 0) {
                switch_state(MPLAYER_STATE_ZERO_VOLUME, mplayer_ctx.last_state);
            }
            break;
        case MPLAYER_STATE_MUTE:
            /* do nothing */
            break;
        default:
            break;
    }
    return ret;
}

int8_t mplayer_pause(mplayer_source_t source)
{
    LPM_RETURN_RET(-1);
    LOGD(TAG, "Enter %s: current state [%d]", __FUNCTION__, mplayer_ctx.cur_state);
    int ret = 0;
    switch (mplayer_ctx.cur_state) {
        case MPLAYER_STATE_PLAYING:
        case MPLAYER_STATE_ZERO_VOLUME:
            if (SOURCE_LOCAL == source) {
                switch_state(mplayer_ctx.cur_state, MPLAYER_STATE_MUTE);
            } else {
                switch_state(mplayer_ctx.cur_state, MPLAYER_STATE_PAUSE);
            }
            break;
        case MPLAYER_STATE_PAUSE:
            if (SOURCE_LOCAL == source) {
                switch_state(mplayer_ctx.cur_state, MPLAYER_STATE_MUTE);
            }
            break;
        case MPLAYER_STATE_MUTE:
            if (SOURCE_LOCAL == source) {
                switch_state(MPLAYER_STATE_MUTE, mplayer_ctx.last_state);
            }
            break;
        default:
            break;
    }
    return ret;
}

int8_t mplayer_resume(mplayer_source_t source)
{
    LPM_RETURN_RET(-1);
    LOGD(TAG, "Enter %s: current state [%d]", __FUNCTION__, mplayer_ctx.cur_state);
    int ret = 0;
    switch (mplayer_ctx.cur_state) {
        case MPLAYER_STATE_PLAYING:
        case MPLAYER_STATE_PAUSE:
            switch_state(mplayer_ctx.cur_state, MPLAYER_STATE_PLAYING);
            break;
        case MPLAYER_STATE_ZERO_VOLUME:
            mplayer_ctx.last_state = MPLAYER_STATE_PLAYING;
            break;
        case MPLAYER_STATE_MUTE:
            /* do nothing */
            break;
        default:
            break;
    }
    return ret;
}

int8_t mplayer_start(mplayer_source_t source, int type, char *url, uint64_t seek_time,
                     uint8_t resume)
{
    LPM_RETURN_RET(-1);
    LOGD(TAG, "Enter %s: current state [%d]", __FUNCTION__, mplayer_ctx.cur_state);
    int8_t       ret = 0;
    unsigned int flags;

    if ((MPLAYER_STATE_MUTE != mplayer_ctx.cur_state) && (MEDIA_SYSTEM == type)) {
        mplayer_ctx.tts_resume = resume;
        aos_event_set(&mplayer_ctx.event_tts_state, 0, AOS_EVENT_AND);
    }
    switch (mplayer_ctx.cur_state) {
        case MPLAYER_STATE_PLAYING:
        case MPLAYER_STATE_PAUSE:
            if (MEDIA_SYSTEM == type) {
                ret = aui_player_seek_play(type, url, seek_time, resume);
            } else {
                if (AUI_PLAYER_PLAYING == aui_player_get_state(MEDIA_SYSTEM)) {
                    aos_event_get(&mplayer_ctx.event_tts_state, EVENT_TTS_PLAYER_FINISHED,
                                  AOS_EVENT_OR_CLEAR, &flags, AOS_WAIT_FOREVER);
                }
                ret = aui_player_seek_play(type, url, seek_time, 0);
                switch_state(mplayer_ctx.cur_state, MPLAYER_STATE_PLAYING);
            }
            break;
        case MPLAYER_STATE_ZERO_VOLUME:
            volume_set(mplayer_ctx.min_vol);
            if (MEDIA_SYSTEM == type) {
                if (MPLAYER_STATE_PAUSE == mplayer_ctx.last_state) {
                    switch_state(mplayer_ctx.cur_state, MPLAYER_STATE_PAUSE);
                } else {
                    /* cannot resume now, only update mplayer status */
                    /* after SYSTEM playback finish, let application decide whether to resume or not */
                    mplayer_ctx.cur_state = MPLAYER_STATE_PLAYING;
                }

                ret = aui_player_seek_play(type, url, seek_time, resume);
                aos_event_get(&mplayer_ctx.event_tts_state,
                              EVENT_TTS_PLAYER_FINISHED | EVENT_TTS_PLAYER_START,
                              AOS_EVENT_OR_CLEAR, &flags, AOS_WAIT_FOREVER);
            } else {
                if (AUI_PLAYER_PLAYING == aui_player_get_state(MEDIA_SYSTEM)) {
                    aos_event_get(&mplayer_ctx.event_tts_state, EVENT_TTS_PLAYER_FINISHED,
                                  AOS_EVENT_OR_CLEAR, &flags, AOS_WAIT_FOREVER);
                }
                ret = aui_player_seek_play(type, url, seek_time, 0);
                switch_state(MPLAYER_STATE_ZERO_VOLUME, MPLAYER_STATE_PLAYING);
            }
            break;
        case MPLAYER_STATE_MUTE:
            /* do nothing */
            break;
        default:
            break;
    }

    return ret;
}

int8_t mplayer_stop(mplayer_source_t source, int type)
{
    LPM_RETURN_RET(-1);
    LOGD(TAG, "Enter %s: current state [%d]", __FUNCTION__, mplayer_ctx.cur_state);
    int ret = 0;
    ret     = stop_play(type);
    return ret;
}

mplayer_state_t mplayer_get_state(void)
{
    return mplayer_ctx.cur_state;
}

int8_t mplayer_vol_get(void)
{
    return (int8_t)auikv_player_vol_save();
}

int8_t mplayer_lpm(uint8_t state)
{
    uint32_t irq_flag = 0;

    irq_flag = csi_irq_save();
    aos_kernel_sched_suspend();
    mplayer_ctx.lpm_flag = state;
    aos_kernel_sched_resume();
    csi_irq_restore(irq_flag);

    return 0;
}

int8_t mplayer_deinit(void)
{
    int8_t ret = 0;
    aos_event_free(&mplayer_ctx.event_tts_state);
    if (NULL != mplayer_ctx.task_media) {
        utask_destroy(mplayer_ctx.task_media);
        mplayer_ctx.task_media = NULL;
    }
    memset(&mplayer_ctx, 0, sizeof(mplayer_type_t));

    return ret;
}